【RPG Maker MV插件编程】【实例教程6】存档的加密解密与保护

您所在的位置:网站首页 undermine 存档 【RPG Maker MV插件编程】【实例教程6】存档的加密解密与保护

【RPG Maker MV插件编程】【实例教程6】存档的加密解密与保护

2023-09-16 01:50| 来源: 网络整理| 查看: 265

作者:Mandarava(鳗驼螺) 微博:@鳗驼螺pro

这篇文章前半部分将研究MV游戏的存档、读档过程,从而实现一个MV游戏存档修改器。后半部分则是实现一个防止存档被修改的MV存档保护插件。

找出MV存档和读档的方式

DataManager 类用于管理数据库和游戏对象,包括游戏的存档、读档。DataManager 使用DataManager.saveGame() 方法来存档,用DataManager.loadGame() 方法来读档。在存档过程中,它会实际调用DataManager.saveGameWithoutRescue() 来保存存档数据。看一下这个方法的具体实现:

DataManager.saveGameWithoutRescue = function(savefileId) { var json = JsonEx.stringify(this.makeSaveContents()); if (json.length >= 200000) { console.warn('Save data too big!'); } StorageManager.save(savefileId, json); this._lastAccessedId = savefileId; var globalInfo = this.loadGlobalInfo() || []; globalInfo[savefileId] = this.makeSavefileInfo(); this.saveGlobalInfo(globalInfo); return true; };

首先,它会先用DataManager.makeSaveContents() 方法将需要存入存档的数据(包括 $gameSystem,$gameScreen,$gameTimer,$gameSwitches,$gameVariables,$gameSelfSwitches,$gameActors,$gameParty,$gameMap,$gamePlayer 等10个全局变量的数据)合并成一个对象contents。DataManager.makeSaveContents的实现代码如下:

DataManager.makeSaveContents = function() { // A save data does not contain $gameTemp, $gameMessage, and $gameTroop. var contents = {}; contents.system = $gameSystem; contents.screen = $gameScreen; contents.timer = $gameTimer; contents.switches = $gameSwitches; contents.variables = $gameVariables; contents.selfSwitches = $gameSelfSwitches; contents.actors = $gameActors; contents.party = $gameParty; contents.map = $gameMap; contents.player = $gamePlayer; return contents; };

然后使用JsonEx.stringify 方法将这个对象进行json序列化转换成json字符串。(说句题外话,从这里也可以看出,如果我们要保存自定义的变量、数据到存档中,只需要以属性的方式添加给这10个全局对象中的任意一个即可,非常简单。)然后再调用StorageManager.save(savefileId, json) 方法将json字符串保存到存档文件中(在读档时,这个json字符串会被反序列化成那10个全局对象)。

再看一下StorageManager.save 方法的实现(如下面的代码)。对于本地数据,它会实际调用saveToLocalFile 方法去保存数据。

StorageManager.save = function(savefileId, json) { if (this.isLocalMode()) { this.saveToLocalFile(savefileId, json); } else { this.saveToWebStorage(savefileId, json); } };

下面的代码是StorageManager.saveToLocalFile 方法的实现。在正式保存前它会用LZString.compressToBase64 方法将json字符串编码成Base64字符串。

StorageManager.saveToLocalFile = function(savefileId, json) { var data = LZString.compressToBase64(json); var fs = require('fs'); var dirPath = this.localFileDirectoryPath(); var filePath = this.localFilePath(savefileId); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); } fs.writeFileSync(filePath, data); };

类似的,对于读档过程,我们最终也会追踪到一个类似的方法,StorageManager.loadFromLocalFile 方法。在这个方法里,它会将存档中的内容使用LZString.decompressFromBase64 方法来还原成json字符串。

StorageManager.loadFromLocalFile = function(savefileId) { var data = null; var fs = require('fs'); var filePath = this.localFilePath(savefileId); if (fs.existsSync(filePath)) { data = fs.readFileSync(filePath, { encoding: 'utf8' }); } return LZString.decompressFromBase64(data); };

所以,实际上MV的存档内容就是使用LZString.compressToBase64 方法编码过的Base64字符串,而存档的解密方法就是用LZString.decompressFromBase64 方法进行反向解码操作。

制作MV存档的修改器

经过以上分析,现在只需要将LZString 的代码复制出来,简单的用HTML+Javascript技术就能做出一个MV存档的解密、加密工具,这个工具我放在github上,有兴趣的可以从 这里 下载。 用这个工具来测试一下MV的存档数据,效果如下图,真实数据都被解密出来了,只需要将真实数据进行一下修改,然后再重新加密,将加密的内容复制回存档保存就完成了存档的修改。

MV存档测试 如何保护存档?

为防止存档被随意修改,可以对存档内容进行加密,在读档时也要相应的作解密操作。通过分析,进行加密操作的最佳位置是在DataManager.saveGameWithoutRescue 方法中进行,当全局对象被序列化成json字符串后,立即对json字符串进行加密。而解密过程相应的放在DataManager.loadGameWithoutRescue 中进行。LZString的作用是对字符串进行压缩,当然你也可以只重写LZString.compressToBase64和LZString.decompressFromBase64方法,在实现压缩/还原的时候同时实现字符串的加密与解密,本质上没有差别,但直接修改LZString 影响面会比较广,所有调用这二个方法的代码都会有影响,包括global.rpgsave 的数据也会被加密。

制作一个存档保护插件

接下来就来制作一个存档保护插件。这里只需要重写DataManager.saveGameWithoutRescue方法,实现json字符串加密,重写DataManager.loadGameWithoutRescue方法,实现json字符串的解密还原即可。完整的代码如下(本插件的最新版本可以在这里下载)。其中encrypt和decrypt方法是字符串的加密、解密方法。加密时,它会先对json字符串先进行一次LZString压缩,然后用凯撒加密算法(本算法修改自 这里)对压缩过的字符串进行加密,解密时就是反向操作。凯撒加解密算法简单、强度不高,好处是不会增加字符串长度,这里 还有个相对高强度的版本,可以设定字符串密码,但缺点是会增加存档内容的长度。你也可以用自己的算法(比如DES, AES等)来代替(PS:如果要更换算法,注意验证算法是否支持对中文的加密解密,如果不支持中文,你可以像这里一样先用LZString对它进行一次压缩操作)。

//============================== // MND_ProtectProfile2.js // Copyright (c) 2017 Mandarava // Homepage: www.popotu.com //============================== /*: * @plugindesc 用于加密存档的插件,可指定加密密码。(v1.0) * @author Mandarava(鳗驼螺) * @version 1.0 * * @param Password * @text 存档密码 * @desc 任意数字,通常取0~26之间的数字。 * @type Number * @default 66 * * @help * 使用时请修改存档密码,不要使用默认值哦! * 本插件采用凯撒加密算法,强度较低,好处是不会增加存档内容长度。可以采取的提高 * 算法强度的方法,包括:对几偶数上的字符采用不同的偏移量,在特定位置添加混淆字 * 符或字符串等。要使用加密强度较高的版本请使用 MND_ProtectProfile.js 插件。 * * by Mandarava(鳗驼螺) */ (function($){ var params=PluginManager.parameters("MND_ProtectProfile2"); var password=Number(params["Password"]) || 66; DataManager.saveGameWithoutRescue = function(savefileId) { var json = JsonEx.stringify(this.makeSaveContents()); if (json.length >= 200000) { console.warn('Save data too big!'); } json=encrypt(json, password); //对json字符串进行加密 StorageManager.save(savefileId, json); this._lastAccessedId = savefileId; var globalInfo = this.loadGlobalInfo() || []; globalInfo[savefileId] = this.makeSavefileInfo(); this.saveGlobalInfo(globalInfo); return true; }; DataManager.loadGameWithoutRescue = function(savefileId) { var globalInfo = this.loadGlobalInfo(); if (this.isThisGameFile(savefileId)) { var json = StorageManager.load(savefileId); json=decrypt(json, password); //对加密过的json字符串进行解密 this.createGameObjects(); this.extractSaveContents(JsonEx.parse(json)); this._lastAccessedId = savefileId; return true; } else { return false; } }; //===字符串加密解密算法========= //凯撒加密算法改自:https://github.com/bukinoshita/caesar-encrypt function numToChar(num){ return String.fromCharCode(97 + num); } function charToNum(char){ return char.charCodeAt(0) - 97; } function caesar(char, shift){ return numToChar(charToNum(char) + (shift % 26)); } function caesarDec(char, shift){ return numToChar(charToNum(char) - (shift % 26)); } function encryptByCaesar(value, shift){ var letters = value.split(''); return letters.map(function (letter) { return caesar(letter, shift); }).join(""); } function decryptByCaesar(value, shift){ var letters = value.split(''); return letters.map(function (letter) { return caesarDec(letter, shift); }).join(""); } /** * 加密字符串 * @param text 要加密的字符串 * @param shift 解密密码(任意数字,通常取0~26之间的数字) * @returns {*} */ function encrypt(text, shift) { var result=LZString.compressToBase64(text); result=encryptByCaesar(result, shift); return result; } /** * 解密字符串 * @param text 要解密的字符串 * @param shift 解密密码(任意数字,通常取0~26之间的数字) */ function decrypt(text, shift) { var result=decryptByCaesar(text, shift); result=LZString.decompressFromBase64(result); return result; } //=========================== })();

现在,可以运行一下游戏,然后保存游戏,退出游戏再加载游戏,一切都没有问题,说明存档、读档都是正常的。然后,再用前面做的MV存档修改工具测试一下存档数据是否能被解密。在开发期间,存档会保存到[项目目录]\save 文件夹下,用记事本打开该文件夹下的名称类似file1.rpgsave、file2.rpgsave 的存档文件,复制其内容,粘贴到存档修改工具的密文框中,点击“解密”,解出来的数据仍然是加过密的字符串,根本无法修改。这样,这个存档保护插件就完成了。

存档解密测试 PS:在DataManager.saveGame 方法中,在存档时,如果玩家是以覆盖旧存档的方式进行新存档的,那么MV会使用StorageManager.backup 方法对被覆盖的旧存档进行一次备份,以便在存档失败时通过StorageManager.restoreBackup 方法恢复。在StorageManager.backup 方法中看似对存档数据又进行了一次LZString.compressToBase64压缩,但际上它在使用StorageManager.loadFromLocalFile 方法读取旧存档数据时,那个方法会对数据进行一次LZString.decompressFromBase64解压。所以,二相抵消,实际上它并没有改变任何数据。所以StorageManager.backup和StorageManager.restoreBackup方法不需要重写。

by Mandarava(鳗驼螺)2017.08.15



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3